iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 5
1

本篇同步發文在個人Blog: 一袋.NET要扛幾樓?打造容器化的ASP.NET Core網站!系列文章 - (5) 建立商品服務的Api - 3

 1. 新增CatalogSettings類別

 在CatalogApi專案的根目錄,新增CatalogSettings類別,裡面只有一個字串類型的ExternalCatalogBaseUrl Property,是用來從appSettings/環境變數讀取該Catalog Api的Base Url。

 在Startup.cs將Configuration注入CatalogSettings參數:

    public void ConfigureServices(IServiceCollection services)
    {
    	// other code...
    	
    	services.Configure<CatalogSettings>(Configuration);
    
    	// other code...
    }

  在appSettings.json,新增ExternalCatalogBaseUrl屬性與值,而值是以launchSettings.json的URL,後續在容器化使用其他的URL:

"ExternalCatalogBaseUrl" :  "http://localhost:13914"

 2. 新增CatalogController類別

 在CatalogApi專案的資料夾Controllers,新增CatalogController類別,並繼承ControllerBase,也增加屬性[Route("api/[controller]")]與[ApiController]。

2.1 注入CatalogContext與自定義的CatalogSettings參數

  使用Constructor的注入方式,將資料庫的Context與自定義的參數寫入Controller。

    private readonly CatalogContext _catalogContext;
    private readonly IOptionsSnapshot<CatalogSettings> _settings;
    
    public CatalogController(CatalogContext catalogContext, IOptionsSnapshot<CatalogSettings> settings)
    {
    	_catalogContext = catalogContext;
    	_settings = settings;
    }

2.2 回傳商品類型的Action

  此Action回傳所有商品類別。

[HttpGet]
[Route("[action]")]
public async Task<IActionResult> CatalogTypes()
{
	var items = await _catalogContext.CatalogTypes.ToArrayAsync();
	return Ok(items);
}

2.3 根據Id回傳特定商品的Action

  此Action接收int Id的參數,並從資料表Catalog找有這Id的商品資訊。但由於資料庫的商品圖片只有存檔,而連結需要被更換,所以在專案新增ViewModels的資料夾,新增一個CatalogItemResponseVM的類別,只定義須回傳到頁面的Property

namespace CatalogApi.ViewModels
{
    public class CatalogItemResponseVM
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public decimal Price { get; set; }
        public string PictureUrl { get; set; }
    }
}

  

  Action和換照片連結的函式:

    private const string pictureUrlTemplate = "/api/picture/{0}";
    
    [HttpGet]
    [Route("items/{id:int}")]
    public async Task<IActionResult> GetItemById(int id)
    {
    	if (id <= 0)
    	{
    		return BadRequest();
    	}
    
    	var item = await _catalogContext.CatalogItems
    		.Select(x => new CatalogItemResponseVM {
    			Description = x.Description,
    			Id = x.Id,
    			Name = x.Name,
    			Price = x.Price,
    			PictureUrl = x.PictureFileName
    		})
    		.SingleOrDefaultAsync(c => c.Id == id);
    
    	if (item != null)
    	{
    		item.PictureUrl = ChangeItemPictureUrl(item.PictureUrl);
    		return Ok(item);
    	}
    
    	return NotFound();
    }
    
    private string ChangeItemPictureUrl(string fileName)
    {
    	return _settings.Value.ExternalCatalogBaseUrl + string.Format(pictureUrlTemplate, fileName);
    }

2.4 查詢有分頁與商品類別過濾的Action

  此Action接收int? catalogTypeId 商品類別編號與分頁的參數,在資料表過濾這些條件,並回傳多筆的商品資訊。由於有分頁功能,在ViewModels底下建立一個共通性的分頁PaginatedItemsViewModel類別

    using System.Collections.Generic;
    
    namespace CatalogApi.ViewModels
    {
        public class PaginatedItemsViewModel<TEntity> where TEntity : class
        {
            public int PageSize { get; set; }
            public int PageIndex { get; set; }
            public long Count { get; set; }
            public IEnumerable<TEntity> Data { get; set; }
    
            public PaginatedItemsViewModel(int pageSize, int pageIndex, long count, IEnumerable<TEntity> data)
            {
                PageSize = pageSize;
                PageIndex = pageIndex;
                Count = count;
                Data = data;
            }
        }
    }

  而Action為預設每頁呈現6筆資料,從第0頁開始查詢:

    //Get api/Catalog/items[?catalogTypeId=&pageSize=4&pageIndex=3]
    [HttpGet]
    [Route("[action]")]
    public async Task<IActionResult> Items(int? catalogTypeId, [FromQuery] int pageSize = 6, [FromQuery] int pageIndex = 0)
    {
    	var root = _catalogContext.CatalogItems.AsQueryable();
    	if (catalogTypeId.HasValue)
    	{
    		root = root.Where(c => c.CatalogTypeId == catalogTypeId);
    	}
    
    	var totalItems = await root
    						.LongCountAsync();
    	var itemsOnPage = await root
    						.Select(x => new CatalogItemResponseVM
    						{
    							Description = x.Description,
    							Id = x.Id,
    							Name = x.Name,
    							Price = x.Price,
    							PictureUrl = x.PictureFileName
    						})
    						.OrderBy(c => c.Name)
    						.Skip(pageSize * pageIndex)
    						.Take(pageSize)
    						.ToListAsync();
    
    	ChangeItemPictureUrls(itemsOnPage);
    	var model = new PaginatedItemsViewModel<CatalogItemResponseVM>(pageIndex, pageSize, totalItems, itemsOnPage);
    	return Ok(model);
    }
    
    private void ChangeItemPictureUrls(List<CatalogItemResponseVM> list)
    {
    	list.ForEach(x => x.PictureUrl = ChangeItemPictureUrl(x.PictureUrl));
    }

2.5 新增商品

  此Action為Http Post,在CatalogItems新增CatalogItem物件,並回傳Status Code 201

    [HttpPost]
    [Route("items")]
    public async Task<IActionResult> CreateProduct([FromBody] CatalogItem product)
    {
    	var item = new CatalogItem
    	{
    		CatalogBrandId = product.CatalogBrandId,
    		CatalogTypeId = product.CatalogTypeId,
    		Description = product.Description,
    		Name = product.Name,
    		PictureFileName = product.PictureFileName,
    		Price = product.Price
    	};
    	_catalogContext.CatalogItems.Add(item);
    	await _catalogContext.SaveChangesAsync();
    	return CreatedAtAction(nameof(GetItemById), new { id = item.Id }, item);
    }

2.6 更新商品

  此Action為Http Put,在CatalogItems更新CatalogItem物件,並回傳Status Code 201

    [HttpPut]
    [Route("items")]
    public async Task<IActionResult> UpdateProduct([FromBody] CatalogItem productToUpdate)
    {
    	var catalogItem = await _catalogContext.CatalogItems
    						.SingleOrDefaultAsync(c => c.Id == productToUpdate.Id);
    	if (catalogItem == null)
    	{
    		return NotFound(new { Message = $"item with id {productToUpdate.Id} not found." });
    	}
    
    	catalogItem = productToUpdate;
    	_catalogContext.CatalogItems.Update(catalogItem);
    	await _catalogContext.SaveChangesAsync();
    
    	return CreatedAtAction(nameof(GetItemById), new { id = productToUpdate.Id }, catalogItem);
    }

2.7 刪除商品

  此Action為Http Delete,在CatalogItems根據Id刪除CatalogItem物件,並回傳Status Code 204

    [HttpDelete]
    [Route("{id}")]
    public async Task<IActionResult> DeleteProduct(int id)
    {
    	var product = await _catalogContext.CatalogItems.SingleOrDefaultAsync(p => p.Id == id);
    	if(product == null)
    	{
    		return NotFound();
    	}
    
    	_catalogContext.CatalogItems.Remove(product);
    	await _catalogContext.SaveChangesAsync();
    	return NoContent();
    }

 3. 新增PictureController類別

  在CatalogApi專案的資料夾Controllers,新增PictureController類別,並繼承ControllerBase,也增加屬性[Route("api/[controller]")]與[ApiController]。

  而它主要是讀取商品的照片,並回傳File Result:

    using Microsoft.AspNetCore.Hosting;
    using Microsoft.AspNetCore.Mvc;
    using System.IO;
    
    namespace CatalogApi.Controllers
    {
        [Route("api/[controller]")]
        [ApiController]
        public class PictureController : ControllerBase
        {
            private readonly IWebHostEnvironment _env;
            public PictureController(IWebHostEnvironment env)
            {
                _env = env;
            }
    
            [HttpGet]
            [Route("{fileName}")]
            public IActionResult GetImage(string fileName)
            {
                var webRoot = _env.WebRootPath;
                var path = Path.Combine(webRoot + "/Pictures/", fileName);
                var buffer = System.IO.File.ReadAllBytes(path);
                return File(buffer, "image/png");
            }
        }
    }

 4. 測試API

  開啟Debug,根據你的Host設定,網址輸入 http://yourhost:yourport/api/catalog/items ,應該要能看到多筆分頁的功能能得到商品資訊:


  下一篇將撰寫Swagger UI的配置與測試API CRUD。


上一篇
[Day 4] 建立商品服務的Api - 2
下一篇
[Day 6] 建立商品服務的Api - 4
系列文
一袋.NET要扛幾樓?打造容器化的ASP.NET Core網站!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言